package resty

import (
	"encoding/json"
	"fmt"
	"net"
	"net/http"
	"time"

	"gitee.com/xxhsjp/x-core/conf"
	"github.com/go-resty/resty/v2"
	"github.com/sirupsen/logrus"
)

func New() *resty.Client {
	return resty.New()
}

func NewDefault() *resty.Client {
	return NewWithConfig(DefaultConfig())
}

func NewWithConfig(c Config) *resty.Client {
	tr := &http.Transport{
		Proxy: nil,
		DialContext: (&net.Dialer{
			Timeout:   c.ConnTimeout,
			KeepAlive: 30 * time.Second,
		}).DialContext,
		ForceAttemptHTTP2:     true,
		MaxIdleConns:          c.MaxIdleConns,
		IdleConnTimeout:       c.IdleConnTimeout,
		TLSHandshakeTimeout:   c.TLSHandshakeTimeout,
		ExpectContinueTimeout: 1 * time.Second,
		MaxConnsPerHost:       c.MaxConnsPerHost,
		MaxIdleConnsPerHost:   c.MaxIdleConnsPerHost,
	}
	client := resty.New()
	client.SetTransport(tr)
	client.SetTimeout(c.Timeout)
	client.SetRetryCount(c.RetryCount)
	client.SetLogger(logrus.StandardLogger())
	if conf.Wrapper.GetBool("http.resty.enableLog") {
		client.OnBeforeRequest(requestMiddleware)
		client.OnAfterResponse(responseMiddleware)
	}
	client.SetHeaders(defaultHeaders())
	// client.SetDebug(true)
	return client
}

func NewWithTansport(tr *http.Transport, timeout time.Duration) *resty.Client {
	client := resty.New()
	client.SetTransport(tr)
	client.SetTimeout(timeout)
	client.SetLogger(logrus.StandardLogger())

	return client
}

type Config struct {
	ConnTimeout         time.Duration
	Timeout             time.Duration
	MaxIdleConns        int
	IdleConnTimeout     time.Duration
	MaxConnsPerHost     int
	MaxIdleConnsPerHost int
	TLSHandshakeTimeout time.Duration
	RetryCount          int
}

func DefaultConfig() Config {
	return Config{
		ConnTimeout:         10 * time.Second,
		Timeout:             60 * time.Second,
		MaxIdleConns:        100,
		IdleConnTimeout:     60 * time.Second,
		MaxConnsPerHost:     0,
		MaxIdleConnsPerHost: 5,
		TLSHandshakeTimeout: 10 * time.Second,
		RetryCount:          3,
	}
}

func requestMiddleware(c *resty.Client, r *resty.Request) error {
	var reqBody string
	body := transformToString(r.Body)
	if len(body) > 10240 {
		reqBody = string(body[:10240]) + "..."
	} else {
		reqBody = string(body)
	}
	logger := logger(nil)
	logger.WithFields(logrus.Fields{
		"url":     r.URL,
		"reqBody": reqBody,
	}).Info("Request")

	return nil
}

func responseMiddleware(c *resty.Client, r *resty.Response) error {
	var respBody string
	if r.Size() > 10240 {
		respBody = string(r.Body()[:10240]) + "..."
	} else {
		respBody = string(r.Body())
	}
	logger := logger(nil)
	logger.WithFields(logrus.Fields{
		"url":      r.Request.URL,
		"status":   r.StatusCode(),
		"latency":  r.Time(),
		"size":     r.Size(),
		"respBody": respBody,
	}).Info("Response")

	return nil
}

func transformToString(data any) string {
	var str string
	switch data := data.(type) {
	case string:
		str = data
	case []byte:
		str = string(data)
	case map[string]any:
		str = fmt.Sprintf("%v", data)
	default:
		if json, err := json.Marshal(data); err == nil {
			str = string(json)
		} else {
			str = fmt.Sprintf("%v", data)
		}
	}

	return str
}

func logger(fields map[string]any) *logrus.Entry {
	return logrus.WithFields(logrus.Fields(fields))
}

func defaultHeaders() map[string]string {
	headers := make(map[string]string, 1)
	headers["x-agent"] = "x-goresty"
	return headers
}
